﻿/*
 * Copyright 2011 Edward L. Stauff.                  Contact: <EdStauff@gmail.com>.
 * 
 * This file, "ListString.cs", is part of the "Stauffware.Common.Core" class library.
 * "Stauffware.Common.Core" is free software: you can redistribute it and/or 
 * modify it under the terms of the GNU Lesser Public License as published by the 
 * Free Software Foundation, either version 3 of the License, or (at your option) 
 * any later version.
 * 
 * "Stauffware.Common.Core" is distributed in the hope that it will be useful, 
 * but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY 
 * or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Lesser Public License for more 
 * details.
 * 
 * You should have received a copy of the GNU Lesser Public License along with 
 * the "Stauffware.Common.Time" sources.  If not, see <http://www.gnu.org/licenses/>.
 */
using System;
using System.Text;
using System.Collections;
using System.Collections.Generic;

namespace Stauffware.Common.Core
{
	/// <summary>
	/// A simple, lightweight class for building a string 
	/// containing a delimited list of items.
	/// </summary>
	/// <example> <![CDATA[
	/// 
	///		object[] justSomeStuff = new object[] { 42, "foo", 1.0/3.0 };
	///		
	///		ListString myList = new ListString(" | ");
	///		foreach (object o in justSomeStuff)
	///			myList.Add(o.ToString());
	/// 
	///		string result = myList.ToString();
	///		// result = "42 | foo | 0.33333333333333"
	/// 
	/// ]]> </example>

	public class ListString
	{
		/// <summary>
		/// Gets the number of items added to the list.
		/// </summary>
		public int Count { get { return this._count; } }
		private int _count;

		private StringBuilder StrBldr;
		private string ListStr;

		private string Prefix = "";
		private string Separator;
		private string Suffix = "";
		private string Empty = "";

		// these are to avoid "if" statements everywhere
		private delegate void Appender (string str);
		private Appender Append;

		//-----------------------------------------------------------------------------
		/// <summary>
		/// Returns a string containing a delimited list of items.
		/// </summary>
		/// <param name="list">the list of items, or null</param>
		/// <param name="prefix">
		///		This will be prepended to the beginning of a NON-EMPTY list.
		/// </param>
		/// <param name="separator">
		///		This will be placed between items.  Note that the separator will not
		///		appear if there is only one item (or no items).  Null is not allowed.
		/// </param>
		/// <param name="suffix">
		///		This will be appended to the end of a NON-EMPTY list.
		/// </param>
		/// <param name="empty">
		///		This will be returned in place of an empty list.  Null is not allowed.
		/// </param>
		/// <param name="countGuess">
		///		An estimate of how many items will be added to this list.
		///		ListString uses this only for optimization; it has no effect on the
		///		output string.
		/// </param>
		/// <param name="stringizer">
		///		This delegate is called to convert each list item to a string.
		///		If the delegate returns null, then the item is ignored.
		///		If missing or null, each item's ToString() method will be called
		///		instead.  If an item is null, then "&lt;NULL&gt;" is substituted.
		/// </param>
		/// <summary>
		/// The stringized items in the list, separated by the separator string.
		/// If the list was empty or null, the "empty" string is returned instead;
		/// otherwise, the list of items are bracketed with the prefix and suffix strings.
		/// May return an empty string, but never returns null.
		/// </summary>

		public static string ToString (IEnumerable list, string prefix, string separator,
										string suffix, string empty, int countGuess,
										Func<object, string> stringizer=null)
		{
			if (stringizer == null) stringizer = DefaultStringizer;

			ListString ls = new ListString(prefix, separator, suffix, empty, countGuess);

			if (list != null)
			{
				foreach (object obj in list)
				{
					string str = stringizer(obj);
					if (str != null)
						ls.Add(str);
				}
			}

			return ls.ToString();
		}

		//-----------------------------------------------------------------------------
		/// <summary>
		/// Returns a string containing a delimited list of items.
		/// </summary>
		/// <param name="list">the list of items</param>
		/// <param name="separator">
		///		This will be placed between items.  Note that the separator will not
		///		appear if there is only one item (or no items).  Null is not allowed.
		/// </param>
		/// <param name="countGuess">
		///		An estimate of how many items will be added to this list.
		///		ListString uses this only for optimization; it has no effect on the
		///		output string.
		/// </param>
		/// <param name="stringizer">
		///		This delegate is called to convert each list item to a string.
		///		If the delegate returns null, then the item is ignored.
		///		If missing or null, each item's ToString() method will be called
		///		instead.  If an item is null, then "&lt;NULL&gt;" is substituted.
		/// </param>
		/// <summary>
		/// The stringized items in the list, separated by the separator string.
		/// If the list was empty or null, an empty string is returned instead.
		/// Never returns null.
		/// </summary>

		public static string ToString (IEnumerable list, string separator, int countGuess,
										Func<object, string> stringizer=null)
		{
			return ToString(list, null, separator, null, "", countGuess, stringizer);
		}

		//-----------------------------------------------------------------------------
		/// <summary>
		/// Returns a string containing a delimited list of items.
		/// </summary>
		/// <param name="list">the list of items</param>
		/// <param name="separator">
		///		This will be placed between items.  Note that the separator will not
		///		appear if there is only one item (or no items).  Null is not allowed.
		/// </param>
		/// <param name="stringizer">
		///		This delegate is called to convert each list item to a string.
		///		If the delegate returns null, then the item is ignored.
		///		If missing or null, each item's ToString() method will be called
		///		instead.  If an item is null, then "&lt;NULL&gt;" is substituted.
		/// </param>
		/// <summary>
		/// The stringized items in the list, separated by the separator string.
		/// If the list was empty or null, an empty string is returned instead.
		/// Never returns null.
		/// </summary>

		public static string ToString (IEnumerable list, string separator,
										Func<object, string> stringizer=null)
		{
			return ToString(list, null, separator, null, "", int.MaxValue, stringizer);
		}

		//-----------------------------------------------------------------------------

		private static string DefaultStringizer (object obj) 
		{ return obj == null ? "<NULL>" : obj.ToString(); }

		//-----------------------------------------------------------------------------

		#region Constructors

		//-----------------------------------------------------------------------------
		/// <summary>
		/// Full-featured constructor.
		/// </summary>
		/// <param name="prefix">
		///		This will be prepended to the beginning of a NON-EMPTY list.
		/// </param>
		/// <param name="separator">
		///		This will be placed between items.  Note that the separator will not
		///		appear if there is only one item (or no items).  Null is not allowed.
		/// </param>
		/// <param name="suffix">
		///		This will be appended to the end of a NON-EMPTY list.
		/// </param>
		/// <param name="empty">
		///		This will be returned in place of an empty list.  Null is not allowed.
		/// </param>
		/// <param name="countGuess">
		///		An estimate of how many items will be added to this list.
		///		ListString uses this only for optimization; it has no effect on the
		///		output string.
		/// </param>

		public ListString (string prefix, string separator, string suffix,
										string empty="", int countGuess=int.MaxValue)
		{
			Require.NotNull(separator);
			Require.NotNull(empty);

			if (countGuess > 10)	// dunno what this magic # should really be)
			{
				this.StrBldr = new StringBuilder();
				this.Append = delegate(string str) { this.StrBldr.Append(str); };
			}
			else
			{
				this.ListStr = "";
				this.Append = delegate(string str) { this.ListStr += str; };
			}

			this.Prefix = (prefix == null) ? "" : prefix;
			this.Separator = separator;
			this.Suffix = (suffix == null) ? "" : suffix;
			this.Empty = empty;
		}

		//-----------------------------------------------------------------------------
		/// <summary>
		/// Constructor.
		/// </summary>
		/// <param name="separator">
		///		This will be placed between items.  Note that the separator will not
		///		appear if there is only one item (or no items).  Null is not allowed.
		/// </param>
		/// <param name="empty">
		///		This will be returned in place of an empty list.  Null is not allowed.
		/// </param>
		/// <param name="countGuess">
		///		An estimate of how many items will be added to this list.
		///		ListString uses this only for optimization; it has no effect on the
		///		output string.
		/// </param>

		public ListString (string separator, string empty, int countGuess=int.MaxValue)
			: this("", separator, "", empty, countGuess) { }

		//-----------------------------------------------------------------------------
		/// <summary>
		/// Constructor.
		/// </summary>
		/// <param name="separator">
		///		This will be placed between items.  Note that the separator will not
		///		appear if there is only one item (or no items).  Null is not allowed.
		/// </param>
		/// <param name="countGuess">
		///		An estimate of how many items will be added to this list.
		///		ListString uses this only for optimization; it has no effect on the
		///		output string.
		/// </param>

		public ListString (string separator, int countGuess=int.MaxValue)
			: this("", separator, "", "", countGuess) { }

		//-----------------------------------------------------------------------------

		#endregion

		//-----------------------------------------------------------------------------
		/// <summary>
		/// Adds one or more items to the list.
		/// </summary>
		/// <param name="items">one or more items to add to the list</param>

		public void Add (params string[] items)
		{
			foreach (string item in items)
				this.AddOne(item);
		}

		//-----------------------------------------------------------------------------

		private void AddOne (string item)
		{
			this.Append(this.Separator + item);
			this._count++;
		}

		//-----------------------------------------------------------------------------
		/// <summary>
		/// Returns the items added so far, separated by the separator string.
		/// If no items have been added, the "empty" string is returned instead;
		/// otherwise, the list of items are bracketed with the prefix and suffix strings.
		/// May return an empty string, but never returns null.
		/// You can add more items after calling ToString().
		/// </summary>

		public override string ToString ( )
		{
			if (this._count == 0)
				return this.Empty;

			string content = (this.StrBldr == null) ? this.ListStr : this.StrBldr.ToString();
			return this.Prefix + content.Substring(this.Separator.Length) + this.Suffix;
		}

		//-----------------------------------------------------------------------------
	}
}
